package com.demo.rpc.aspect;

import com.demo.holder.ThreadContextHolder;
import com.demo.rpc.annotation.RPCTransactional;
import com.demo.rpc.dto.BaseRPCDTO;
import com.demo.rpc.exception.RPCException;
import com.demo.rpc.manager.RPCTransactionalManager;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.sql.Connection;
import java.sql.SQLException;

/**
 * Created by hunter
 *
 * @Description 分布式事务切面
 */
@Aspect
@Component
@Order // 在transactional事务切面之后
public class RPCTransactionalAspect {

    @Autowired
    private RPCTransactionalManager rpcTransactionalManager;

    /**
     * 注解前, 开启一个事务
     *
     * @param joinPoint
     * @param rpcTransactional
     * @throws SQLException
     */
    @Before(value = "@annotation(rpcTransactional)")
    public void before(JoinPoint joinPoint, RPCTransactional rpcTransactional) throws SQLException {
        System.out.println("~------- Trigger rpcTransactional before ...");

        // 拿到或创建对应的事务id, 开始创建执行分布式任务, 有说明是被调用方, 无说明是起始方
        BaseRPCDTO baseRPCDTO = this.getBaseRPCDTOArg(joinPoint.getArgs());
        String transactionId;
        if (baseRPCDTO == null || baseRPCDTO.getTransactionId() == null) {
            transactionId = rpcTransactionalManager.createRPCTransactional();
        } else {
            transactionId = baseRPCDTO.getTransactionId();
        }
        ThreadContextHolder.setTransactionId(transactionId);
        rpcTransactionalManager.startRPCTransactionalTask(transactionId);
    }

    /**
     * 注解后, 完成当前事务任务
     *
     * @param rpcTransactional
     * @throws SQLException
     */
    @After(value = "@annotation(rpcTransactional)")
    public void after(RPCTransactional rpcTransactional) throws SQLException {
        System.out.println("~------- Trigger rpcTransactional after ...");
        // 方法执行完成后表示当前方法正常结束, 完成本地分布式事务任务
        rpcTransactionalManager.completeRPCTransactionalTask(ThreadContextHolder.getTransactionId());
    }

    /**
     * 本地方法执行出错后, 立即打断当前事务
     *
     * @param rpcTransactional
     */
    @AfterThrowing(value = "@annotation(rpcTransactional)")
    public void afterThrowing(RPCTransactional rpcTransactional) {
        System.out.println("~------- Trigger rpcTransactional afterThrowing ...");
        // 方法异常了要打断当前分布式事务
        rpcTransactionalManager.interruptedRPCTransactionalTask(ThreadContextHolder.getTransactionId());
        // 清除事务id
        ThreadContextHolder.removeTransactionId();
    }

    /**
     * 环绕事件, 处理分布式事务主体, 已废弃
     *
     * @param joinPoint
     * @param rpcTransactional
     */
    // @Around(value = "@annotation(rpcTransactional)")
    public Object around(ProceedingJoinPoint joinPoint, RPCTransactional rpcTransactional) throws SQLException {
        System.out.println("Trigger rpcTransactional ...");

        // 获取数据库连接, 设置为手动提交
        // final Connection connection = dataSource.getConnection();
        final Connection connection = null;
        if (connection == null) {
            throw new RPCException("RPCTransactional does not get connection ...");
        }
        final boolean autoCommit = connection.getAutoCommit();
        connection.setAutoCommit(false);

        // 拿到或创建对应的事务id, 开始创建执行分布式任务
        BaseRPCDTO baseRPCDTO = this.getBaseRPCDTOArg(joinPoint.getArgs());
        final String transactionId;
        if (baseRPCDTO == null || baseRPCDTO.getTransactionId() == null) {
            transactionId = rpcTransactionalManager.createRPCTransactional();
            ThreadContextHolder.setTransactionId(transactionId);
        } else {
            transactionId = baseRPCDTO.getTransactionId();
        }
        ThreadContextHolder.setTransactionId(transactionId);
        rpcTransactionalManager.startRPCTransactionalTask(transactionId);

        try {
            // 尝试执行并返回
            Object object = joinPoint.proceed();
            // 完成当前任务
            rpcTransactionalManager.completeRPCTransactionalTask(transactionId);

            // 执行过后启动线程监听事务
            new Thread(() -> {
                // 等待其它事务完成, 提交事务、恢复初始参数并关闭
                try {
                    rpcTransactionalManager.waitRPCTransactionalTask(transactionId);
                    // 远程调用事务正常的处理
                    connection.commit();
                } catch (SQLException e) {
                    e.printStackTrace();
                } catch (RPCException e) { // 远程调用事务异常处理
                    e.printStackTrace();

                    // 失败了要回滚
                    try {
                        connection.rollback();
                    } catch (SQLException e1) {
                        e1.printStackTrace();
                    }
                } finally {
                    try {
                        connection.setAutoCommit(autoCommit);
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }).start();

            // 返回
            return object;
        } catch (Throwable throwable) { // 这里是本地执行异常后的处理
            throwable.printStackTrace();

            // 失败了要回滚
            connection.rollback();
            // 设置回connection最初的提交状态
            connection.setAutoCommit(autoCommit);
            // 执行关闭方法
            connection.close();

            rpcTransactionalManager.interruptedRPCTransactionalTask(transactionId);

            throw new RPCException("RPCTransactional occur a exception ...");
        } finally {
            // 清除线程变量
            // ThreadContextHolder.removeConnection();
            ThreadContextHolder.removeTransactionId();
        }
    }

    /**
     * 获取切面方法的BaseRPCDTO 请求体
     *
     * @param args
     * @return
     */
    private BaseRPCDTO getBaseRPCDTOArg(Object[] args) {
        if (args == null || args.length == 0) {
            return null;
        }

        BaseRPCDTO baseRPCDTO = null;
        for (Object arg : args) {
            if (arg instanceof BaseRPCDTO) {
                baseRPCDTO = (BaseRPCDTO) arg;
                break;
            }
        }

        return baseRPCDTO;
    }
}
